using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX.Direct3D;
using System.Windows.Forms;
using System.Drawing;
using System.Xml;

namespace Emlyn.Applications.Scratch.Graphics.Tools
{
    public class DirectXWrapper: IDisposable
    {
        PresentParameters _presentParameters;

        public Device _device;

        public Surface _backbuffer = null;

        public DirectXWrapper(int aWidth, int aHeight, Control aCtl)
        {
            DeviceSwapEffect = SwapEffect.Discard;
            Windowed = false;
            DeviceBackBufferCount = 1;
            DeviceCreateFlags = CreateFlags.SoftwareVertexProcessing;

            DoInit(aWidth, aHeight, aCtl);
        }

        public void ReInit(int aWidth, int aHeight, Control aCtl)
        {
            DoDispose();
            DoInit(aWidth, aHeight, aCtl);
        }

        void DoInit(int aWidth, int aHeight, Control aCtl)
        {
            InitDirectX(aWidth, aHeight, aCtl);
            _backbuffer = GetBackBuffer();
        }

        void DoDispose()
        {
            if (_backbuffer != null && !_backbuffer.Disposed)
                _backbuffer.Dispose();
        }

        void InitDirectX(int aWidth, int aHeight, Control aCtl)
        {
            _presentParameters = new PresentParameters();

            AdapterInformation ladapterInformation =
                Manager.Adapters[0];

            _presentParameters.SwapEffect = SwapEffect.Discard;
            _presentParameters.Windowed = true;
            _presentParameters.BackBufferWidth = aWidth;
            _presentParameters.BackBufferHeight = aHeight;
            _presentParameters.BackBufferCount = 1;
            _presentParameters.BackBufferFormat =
                GetCurrentDisplayMode().Format;

            DeviceType ldeviceType = GetSupportedDeviceType();

            _device = new Device(
                0,
                ldeviceType,
                aCtl,
                CreateFlags.SoftwareVertexProcessing,
                _presentParameters
                );
        }

        private DisplayMode GetCurrentDisplayMode()
        {
            AdapterInformation ladapterInformation =
                Manager.Adapters[0];

            return ladapterInformation.CurrentDisplayMode;
        }

        private Surface GetBackBuffer()
        {
            return _device.GetBackBuffer(0, 0, BackBufferType.Mono);
        }

        private DeviceType GetSupportedDeviceType()
        {
            DeviceType retval = DeviceType.Hardware;

            try
            {
                Caps lcaps =
                Manager.GetDeviceCaps(0, retval);
            }
            catch
            {
                try
                {
                    retval = DeviceType.Software;
                    Caps lcaps =
                    Manager.GetDeviceCaps(0, retval);
                }
                catch
                {
                    try
                    {
                        retval = DeviceType.Reference;
                        Caps lcaps =
                        Manager.GetDeviceCaps(0, retval);
                    }
                    catch
                    {
                        throw new ApplicationException("Can't determine device type.");
                    }
                }
            }

            return retval;
        }

        public void DrawOntoDestinationSurface(
            Surface aSource,
            Rectangle aSourceRect,
            Surface aTarget,
            Point aTargetPoint
            )
        {
            // make sure aSourcerect doesn't lie outside aSource
            Rectangle lSourceRect2 = new Rectangle(0, 0, aSource.Description.Width, aSource.Description.Height);
            aSourceRect.Intersect(lSourceRect2);

            Rectangle lTargetRect = new Rectangle(
                    aTargetPoint,
                    aSourceRect.Size
                );

            DrawOntoDestinationSurface(aSource, aSourceRect, aTarget, lTargetRect);
        }

        public void DrawOntoDestinationSurface(
            Surface aSource,
            Rectangle aSourceRect,
            Surface aTarget,
            Rectangle aTargetRect
            )
        {
            if (aSource == null)
                throw new ArgumentNullException("aSource");

            if (aSource.Disposed)
                throw new ArgumentException("aSource.Disposed == true");

            if (aTarget == null)
                throw new ArgumentNullException("aTarget");

            if (aTarget.Disposed)
                throw new ArgumentException("aTarget.Disposed == true");

            // make sure aSourcerect doesn't lie outside aSource
            Rectangle lSourceRect2 = new Rectangle(0, 0, aSource.Description.Width, aSource.Description.Height);
            aSourceRect.Intersect(lSourceRect2);

            Rectangle lTargetSurfaceRect = new Rectangle(0, 0, aTarget.Description.Width, aTarget.Description.Height);
            Rectangle lTargetRectClippedToTarget = new Rectangle(aTargetRect.Location, aTargetRect.Size);

            lTargetRectClippedToTarget.Intersect(lTargetSurfaceRect);

            if (lTargetRectClippedToTarget.Height > 0 && lTargetRectClippedToTarget.Width > 0)
            {
                Rectangle lSourceCoordBackBufferRect = TranslateRectangle(lTargetSurfaceRect, aTargetRect, aSourceRect);
                aSourceRect.Intersect(lSourceCoordBackBufferRect);

                _device.StretchRectangle(
                    aSource, aSourceRect, aTarget, lTargetRectClippedToTarget, TextureFilter.None
                );
            }
        }

        public void DrawOntoBackBuffer(
            Surface aSource,
            Rectangle aSourceRect,
            Rectangle aTargetRect
            )
        {
            if (_backbuffer == null || _backbuffer.Disposed)
            {
                _backbuffer = GetBackBuffer();
            }

            DrawOntoDestinationSurface(aSource, aSourceRect, _backbuffer, aTargetRect);
        }

        public void DrawOntoBackBuffer(
            Surface aSource,
            Rectangle aSourceRect,
            Point aTargetPoint
            )
        {
            // make sure aSourcerect doesn't lie outside aSource
            Rectangle lSourceRect2 = new Rectangle(0, 0, aSource.Description.Width, aSource.Description.Height);
            aSourceRect.Intersect(lSourceRect2);

            Rectangle lTargetRect = new Rectangle(
                    aTargetPoint,
                    aSourceRect.Size
                );

            DrawOntoBackBuffer(aSource, aSourceRect, lTargetRect);
        }

        public Rectangle TranslateRectangle(Rectangle aSourceRect, Rectangle aCoordSystemFrom, Rectangle aCoordSystemTo)
        {
            Point lp1 = new Point(
                TranslateCoordInt(aSourceRect.Left, aCoordSystemFrom.Left, aCoordSystemFrom.Right, aCoordSystemTo.Left, aCoordSystemTo.Right),
                TranslateCoordInt(aSourceRect.Top, aCoordSystemFrom.Top, aCoordSystemFrom.Bottom, aCoordSystemTo.Top, aCoordSystemTo.Bottom)
            );

            Point lp2 = new Point(
                TranslateCoordInt(aSourceRect.Right, aCoordSystemFrom.Left, aCoordSystemFrom.Right, aCoordSystemTo.Left, aCoordSystemTo.Right),
                TranslateCoordInt(aSourceRect.Bottom, aCoordSystemFrom.Top, aCoordSystemFrom.Bottom, aCoordSystemTo.Top, aCoordSystemTo.Bottom)
            );

            Rectangle retval = new Rectangle(
                lp1, new Size(lp2.X - lp1.X, lp2.Y - lp1.Y)
            );

            return retval;
        }

        public double TranslateCoord(double CoordValue, double aCoordFromLeft, double aCoordFromRight, double aCoordToLeft, double aCoordToRight)
        {
            return ((CoordValue - aCoordFromLeft) * (aCoordToRight - aCoordToLeft)) / (aCoordFromRight - aCoordFromLeft) + aCoordToLeft;
        }

        public int TranslateCoordInt(double CoordValue, double aCoordFromLeft, double aCoordFromRight, double aCoordToLeft, double aCoordToRight)
        {
            return Convert.ToInt32(Math.Round(((CoordValue - aCoordFromLeft) * (aCoordToRight - aCoordToLeft)) / (aCoordFromRight - aCoordFromLeft) + aCoordToLeft));
        }

        public void Clear(Color aClearColour)
        {
            _device.Clear(ClearFlags.Target, aClearColour, 0, 0);
        }

        public Surface LoadSurfaceFromFile(string aFilename)
        {
            Surface retval = null;

            retval = LoadSurfaceFromBitmap(new Bitmap(aFilename));

            return retval;
        }

        public Surface LoadSurfaceFromBitmap(Bitmap aBitmap)
        {
            Surface retval = null;

            retval = new Surface(_device, aBitmap, Pool.Default);

            return retval;
        }

        public Surface CreateImageSurface(int aWidth, int aHeight)
        {
            Surface lretval = 
                _device.CreateOffscreenPlainSurface(
                    aWidth, aHeight,
                    GetCurrentDisplayMode().Format,
                    Pool.Default
            );

            return lretval;
        }

        public void Present()
        {
            _device.Present();
        }

        public Texture LoadTextureFromFile(string aFilename)
        {
            Texture retval = null;

            retval = Microsoft.DirectX.Direct3D.TextureLoader.FromFile(
                _device, 
                aFilename,
                0,
                0,
                0,
                Usage.Dynamic,
                Format.Unknown,
                Pool.Default,
                Filter.None,
                Filter.None,
                0x6A4C30
                );

            return retval;
        }

        private SwapEffect _deviceSwapEffect;

        public SwapEffect DeviceSwapEffect
        {
            get { return _deviceSwapEffect; }
            set { _deviceSwapEffect = value; }
        }

        private bool _windowed;

        public bool Windowed
        {
            get { return _windowed; }
            set { _windowed = value; }
        }

        private int _deviceBackBufferCount;

        public int DeviceBackBufferCount
        {
            get { return _deviceBackBufferCount; }
            set { _deviceBackBufferCount = value; }
        }

        private CreateFlags _deviceCreateFlags;

        public CreateFlags DeviceCreateFlags
        {
            get { return _deviceCreateFlags; }
            set { _deviceCreateFlags = value; }
        }


        public void LoadFromXml(XmlNode aNode)
        {
            DeviceSwapEffect = (SwapEffect)Enum.Parse(typeof(SwapEffect), ReadKey(aNode, "SwapEffect", SwapEffect.Discard.ToString()));
            Windowed = Boolean.Parse(ReadKey(aNode, "Windowed", false.ToString()));
            DeviceBackBufferCount = Int32.Parse(ReadKey(aNode, "BackBufferCount", (1).ToString()));
            DeviceCreateFlags = (CreateFlags)Enum.Parse(typeof(CreateFlags), ReadKey(aNode, "CreateFlags", CreateFlags.SoftwareVertexProcessing.ToString()));
        }
        
        public void SaveToXml(XmlNode aNode)
        {
            XmlDocument ldoc = aNode.OwnerDocument;

            XmlNode lchildNode;

            aNode.AppendChild(ldoc.CreateComment("SwapEffect can be Copy, Discard or Flip"));
            aNode.AppendChild(CreateKey(ldoc, "SwapEffect", Convert.ToString(DeviceSwapEffect)));
            aNode.AppendChild(ldoc.CreateComment("Windowed can be True or False"));
            aNode.AppendChild(CreateKey(ldoc, "Windowed", Convert.ToString(Windowed)));
            aNode.AppendChild(ldoc.CreateComment("BackBufferCount can be any positive integer"));
            aNode.AppendChild(CreateKey(ldoc, "BackBufferCount", Convert.ToString(DeviceBackBufferCount)));
            string lcomment = "";
            foreach (string lname in Enum.GetNames(typeof(CreateFlags)))
            {
                if (lcomment.Length > 0)
                    lcomment += ", ";
                lcomment += lname;
            }
            aNode.AppendChild(ldoc.CreateComment("CreateFlags can be one of " + lcomment));
            aNode.AppendChild(CreateKey(ldoc, "CreateFlags", Convert.ToString(DeviceCreateFlags)));
        }

        private XmlNode CreateKey(XmlDocument aDoc, string aKey, string aValue)
        {
            XmlElement lelem = aDoc.CreateElement(aKey);
            lelem.InnerText = aValue;
            return lelem;
        }

        private string ReadKey(XmlNode aNode, string aKey, string aDefault)
        {
            string retval = aDefault;

            XmlNode lnode = aNode.SelectSingleNode(aKey);
            if (lnode != null)
            {
                retval = aNode.InnerText.Trim();
            }

            return retval;
        }

        #region IDisposable Members

        public void Dispose()
        {
            DoDispose();
        }

        #endregion
    }
}
